package com.zf.common.web.config;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;

/**
 * RestTemplate配置：
 * <p>
 * 官方文档：https://docs.spring.io/spring-boot/docs/2.7.18/reference/html/io.html#io.rest-client
 *
 * @author Zhou Feng
 */
@Configuration
public class RestTemplateConfig {

    /**
     * 定义 restTemplate
     * 加 LoadBalanced 注解后，该 RestTemplate 会自动使用使用服务发现和负载均衡，如果用的是nacos，则使用nacos作为服务发现中心，
     * 添加了 LoadBalanced 注解后，就不能通过uri调用其他服务了。
     * <p>
     * <p>
     * eg: http://service-id/hello
     * <p>
     * {@link LoadBalancerAutoConfiguration } 自动配置类内部逻辑：对添加了 LoadBalanced 注解的 RestTemplate 会自动注入一个 LoadBalancerInterceptor 拦截器，
     * 其内部实现了自动根据 service-id 获取地址的逻辑。
     *
     * <pre>
     * {@code
     *     // 通过服务名调用
     *     String res = restTemplateByServiceId.getForObject("http://gateway-server-sample/test", String.class);
     * }
     * </pre>
     */
    @LoadBalanced
    @Bean("restTemplateByServiceId")
    public RestTemplate restTemplateByServiceId(RestTemplateBuilder restTemplateBuilder) {
        // // 方式 1：
        // RestTemplate restTemplate = new RestTemplate();
        // // 添加请求拦截器
        // restTemplate.getInterceptors().add(traceIdRestTemplateInterceptor());
        // return restTemplate;

        // 方式 2：
        restTemplateBuilder = restTemplateBuilder.additionalInterceptors(traceIdRestTemplateRequestInterceptor());
        return restTemplateBuilder.build();
    }


    /**
     * 定义 restTemplate
     * 不加 @LoadBalanced 注解， 该 RestTemplate 就不能使用服务发现和负载均衡，只能通过uri调用其他服务
     * <p>
     * <p>
     * eg: http://127.0.0.1:9999/hello
     *
     * <pre>
     * {@code
     *     // 1：直接通过uri调用
     *     String res = restTemplateByUri.getForObject("http://localhost:8818/test/hello", String.class);
     *
     *     // 2：手动选择服务
     *     @Autowired
     *     private LoadBalancerClient loadBalancerClient;
     *
     *     ServiceInstance serviceInstance = loadBalancerClient.choose("gateway-server-sample");
     *     // 拼接URL
     *     String url = String.format("http://%s:%s/test", serviceInstance.getHost(), serviceInstance.getPort());
     *     String res = restTemplateByUri.getForObject(url, String.class);
     * }
     * </pre>
     */
    @Bean("restTemplateByUri")
    public RestTemplate restTemplateByUri(RestTemplateBuilder restTemplateBuilder) {
        // 方式 1：
        RestTemplate restTemplate = new RestTemplate();
        // 添加请求拦截器
        restTemplate.getInterceptors().add(traceIdRestTemplateRequestInterceptor());
        return restTemplate;

        // // 方式 2：
        // restTemplateBuilder = restTemplateBuilder.additionalInterceptors(traceIdRestTemplateInterceptor());
        // return restTemplateBuilder.build();
    }


    @Bean("traceIdRestTemplateRequestInterceptor")
    public TraceIdRestTemplateRequestInterceptor traceIdRestTemplateRequestInterceptor() {
        return new TraceIdRestTemplateRequestInterceptor();
    }


    /**
     * 自定义 RestTemplate 的请求拦截器，可以定义多个，都会生效
     */
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public static class TraceIdRestTemplateRequestInterceptor implements ClientHttpRequestInterceptor {
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (Objects.nonNull(requestAttributes)) {
                // 从当前的请求中获取请求头 TRACE_ID
                HttpServletRequest orgRequest = requestAttributes.getRequest();
                String traceId = orgRequest.getHeader(BasicConstants.TRACE_ID);
                if (!StringUtils.hasText(traceId)) {
                    // 如果没有在请求头中获取 TRACE_ID，则去 request的Attribute 中取 TRACE_ID
                    Object attribute = orgRequest.getAttribute(BasicConstants.TRACE_ID);
                    traceId = Objects.isNull(attribute) ? null : String.valueOf(attribute);
                }
                if (StringUtils.hasText(traceId) && !request.getHeaders().containsKey(BasicConstants.TRACE_ID)) {
                    // 给 RestTemplate的请求 设置 TRACE_ID 请求头
                    request.getHeaders().set(BasicConstants.TRACE_ID, traceId);
                }
            }
            // 由 org.springframework.http.client.InterceptingClientHttpRequest.InterceptingRequestExecution#execute 实现请求的发送，
            // 该方法先会执行所有的 ClientHttpRequestInterceptor#intercept，直到执行完最后一个 ClientHttpRequestInterceptor后，才会真正发送请求
            return execution.execute(request, body);
        }
    }
}
